/**
 * @file cpu.c
 * CPU source for TrayInfo.
 */

/* CPU usage support added by Laszlo Ashin <kodest@gmail.com> */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>

#include "trayinfo.h"
#include "utils.h"

/** Source handle. */
static source_handle_t *cpu_sh;

/** Maximal length of a process' name */
#define PROCESS_NAME_SIZE	16

/** 
 * Linked list item containing a process name.
 * This structure is used to group the processes by their name.
 */
typedef struct name_s {

	/** Name of the process(es) */
	char name[PROCESS_NAME_SIZE];

	/** Cumulated time of the processes spent in running state. */
	long unsigned sumtime;

	/** Flag to sign the process group has been accessed. */
	int accessed;

	struct name_s *next;

} name_t;

/** Linked list item containst information about a concrete process. */
typedef struct proc_s {

	/** Process IDentifier  */
	int pid;

	/** Pointer to process name structure */
	name_t *name;

	/** Time spent in running state. */
	long unsigned curtime, lasttimes;

	/** Flag to sign the process has been accessed. */
	int accessed;

	struct proc_s *next;

} proc_t;

static int limit;
static proc_t *first_proc;
static name_t *first_name;

/**
 * Returns the data about the most cpu intensive process.
 */
void
evilproc_scan(/*@out@*/ char *retname, /*@out@*/ long unsigned *rettime)
{
	DIR *d;
	struct dirent *e;
	proc_t *pp, **ppp;
	name_t *np, **pnp;

	d = opendir("/proc");
	if (d == NULL) {
		return;
	}

	for (pp = first_proc; pp; pp = pp->next) {
		pp->accessed = 0;
	}
	for (np = first_name; np; np = np->next) {
		np->accessed = 0;
		np->sumtime = 0;
	}

	while ((e = readdir(d))) {
		int pid;
		char buf[256];
		int n;
		FILE *fp;
		char *name, *p;
		char status;
		long unsigned utime, stime;

		pid = atoi(e->d_name);
		if (pid < 1) continue;

		n = snprintf(buf, sizeof(buf), "/proc/%d/stat", pid);
		if (n < 1 || n >= sizeof(buf)) {
			continue;
		}

		fp = fopen(buf, "rt");
		if (fp == NULL) continue;
		if (fgets(buf, sizeof(buf), fp) != buf) goto fclo;

		if (atoi(buf) != pid) continue;

		name = strchr(buf, '(');
		if (!name) goto fclo;
		++name;

		p = strchr(name, ')');
		if (p == NULL) goto fclo;
		*p++ = '\0';

		/* trim a possible "/x" ending from the name (cpu number) */
		{
			char *q = strchr(name, '/');

			if (q) {
				*q = '\0';
			}
		}

		n = sscanf(p,
			" %c %*u %*u %*u %*u %*u %*u %*u %*u %*u %*u %lu %lu",
			&status, &utime, &stime
		);
		if (n != 3) continue;

		pp = first_proc;
		while (pp != NULL && pp->pid != pid) {
			pp = pp->next;
		}
		if (pp == NULL) {
			pp = NEW(proc_t);
			if (!pp) continue;
			pp->pid = pid;
			pp->name = NULL;
			pp->lasttimes = 0;
			pp->next = first_proc;
			first_proc = pp;
		}
		pp->accessed = 1;
		pp->curtime = utime + stime - pp->lasttimes;
		pp->lasttimes = utime + stime;

		/* check if the associated name is invalid */
		if (pp->name == NULL ||
		  strncmp(pp->name->name, name, PROCESS_NAME_SIZE - 1) != 0) {
			/* we need to search the matching name */
			np = first_name;
			while (np != NULL &&
			  strncmp(np->name, name, PROCESS_NAME_SIZE - 1) != 0) {
				np = np->next;
			}
			if (np == NULL) {
				np = NEW(name_t);
				if (!np) continue;
				strncpy(np->name, name, PROCESS_NAME_SIZE);
				np->name[PROCESS_NAME_SIZE - 1] = '\0';
				np->sumtime = 0;
				np->accessed = 0;
				np->next = first_name;
				first_name = np;
			}
			pp->name = np;
		}
		pp->name->sumtime += pp->curtime;
		++(pp->name->accessed);

	fclo:	fclose(fp);
	}
	closedir(d);

	/* All unaccessed process must be dead. */
	pp = first_proc;
	ppp = &first_proc;
	while (pp) {
		if (!pp->accessed) {
			proc_t *q = pp;

			pp = q->next;
			*ppp = pp;
			free(q);
		} else {
			ppp = &(pp->next);
			pp = pp->next;
		}
	}
	/* All unaccessed process group must be dead. */
	np = first_name;
	pnp = &first_name;
	while (np) {
		if (!np->accessed) {
			name_t *q = np;

			np = q->next;
			*pnp = np;
			free(q);
		} else {
			pnp = &(np->next);
			np = np->next;
		}
	}

	/* Search the name with maximal sumtime. */
	{
		name_t *max = NULL;

		for (np = first_name; np != NULL; np = np->next) {
			if (!max || np->sumtime > max->sumtime) {
				max = np;
			}
		}

		if (max != NULL) {
			strncpy(retname, max->name, PROCESS_NAME_SIZE - 1);
			retname[PROCESS_NAME_SIZE - 1] = '\0';
			*rettime = max->sumtime;
		}
	}
}

static int
cpu_write(double now, int fd)
{
	static unsigned used, total, old_used, old_total;
	unsigned ld[4];
	int i;
	FILE *f;
	char evilproc[PROCESS_NAME_SIZE];
	unsigned long sumtime;
	
	f = fopen("/proc/stat", "r");
	if (f == NULL) {
		return 0;
	}
	i = fscanf(f, "%*s %u %u %u %u", &ld[0], &ld[1], &ld[2], &ld[3]);
	fclose(f);
	if (i != 4) {
		return 0;
	}
	used = ld[0] + ld[1] + ld[2];
	total = used + ld[3];
	if (total != old_total) {
		i = (used - old_used) * 99 / (total - old_total);
	} else {
		i = 0;
	}

	if (i >= limit) {
		evilproc_scan(evilproc, &sumtime);
		show("%s>%2d%%", evilproc, i);
	} else {
		show("%2d%%", i);
	}

	old_used = used;
	old_total = total;

	return 1;
}

static int
cpu_init(source_handle_t *sh)
{
	limit = 50;
	first_name = NULL;
	first_proc = NULL;
	return 0;
}

static void
cpu_done(void)
{
	while (first_name != NULL) {
		name_t *p = first_name;
		first_name = p->next;
		free(p);
	}
	while (first_proc != NULL) {
		proc_t *p = first_proc;
		first_proc = p->next;
		free(p);
	}
}

static int
cpu_cfg_limit(const char *s)
{
	return cfgstr2int(s, &limit);
}

int
init_module(module_phase_t phase)
{
	switch (phase) {
	case MODULE_PHASE_INIT: {
		source_t source = {
			.name = "cpu",
			.init = cpu_init,
			.done = cpu_done,
			.write = cpu_write,
		};
		cpu_sh = register_source(&source);
		if (cpu_sh == NULL) {
			return !0;
		}
		register_config_handler("cpu-limit", cpu_cfg_limit);
		break;
	}
	case MODULE_PHASE_EXIT:
		forget_config_handler("cpu-limit", cpu_cfg_limit);
		forget_source(cpu_sh);
		break;
	}
	return 0;
}
